/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.display.gui.components
{
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.ui.Keyboard;
	import flash.utils.getDefinitionByName;
	import marcel.core.Plugin;
	import marcel.debug.*;
	import marcel.display.gui.form.IFormComponent;
	import marcel.display.View;
	
	/**
	* Dispatched when the value has changed
	* @eventType flash.events.Event.CHANGE
	*/
	[Event(name = "change", type = "flash.events.Event")]
	
	/**
	 * A List component class to use in forms.
	 * The graphics can by changed by editing the ListItemRenderer Class.
	 * @author Alexandre Croiseaux
	 */
	public class List extends View implements IFormComponent
	{
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected var _scroller:Scroller;
		/**
		 * @private
		 */
		protected var _container:View;
		/**
		 * @private
		 */
		protected var _width:uint;
		/**
		 * @private
		 */
		protected var _data:Array;
		/**
		 * @private
		 */
		protected var _selectedIndexes:/*int*/Array;
		/**
		 * @private
		 */
		protected var _startIndex:int;
		/**
		 * @private
		 */
		protected var _rendererClass:Class;
		/**
		 * @private
		 */
		protected var _resizeTracker:Boolean;
		
		public var allowMultiple:Boolean = false;
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		/**
		 * Constructs a new List instance
		 * @param	width	The width of the combobox
		 * @param	numItemsVisible	The number of items visible in the list
		 */
		public function List(width:uint = 150, numItemsVisible:uint = 5, rendererClassOrClassName:* = "marcel.display.gui.components.ListItemRenderer", scrollTrackOnly:Boolean = true)
		{
			_container = new View();
			_container.orientation = ORIENTATION_VERTICAL;
			_container.gap = 0;
			addChild(_container);
			
			try
			{
				if (rendererClassOrClassName is Class) _rendererClass = rendererClassOrClassName;
				else _rendererClass = getDefinitionByName(rendererClassOrClassName) as Class;
			}
			catch (e:Error)
			{
				debugw("Renderer class not found (" + rendererClassOrClassName + ") => using marcel.display.gui.components.ListItemRenderer");
				_rendererClass = ListItemRenderer;
			}
			
			_selectedIndexes = [];
			_width = width;
			_data = [];
			this.numVisibleItems = numItemsVisible;
			
			_scroller = new Scroller(_container.height, scrollTrackOnly);
			_scroller.addEventListener(Event.CHANGE, onScroll);
			_scroller.x = width - _scroller.width;
			addChild(_scroller);
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Indicates nhe number of items visible in the list
		 */
		public function set numVisibleItems(num:uint):void
		{
			var item:ListItemRenderer;
			
			while (_container.numChildren > 0)
			{
				item = _container.removeChildAt(0) as ListItemRenderer;
				item.removeEventListener(MouseEvent.CLICK, onItemClicked);
				item = null;
			}
			
			for (var i:int = 0; i < num; i++)
			{
				item = createItem();
				item.index = i;
				item.addEventListener(MouseEvent.CLICK, onItemClicked);
				item.width = _width;
				_container.addChild(item);
				if (i >= _data.length) item.lock(true);
			}
			
			if (_scroller)
			{
				_scroller.setHeight(_container.height);
				_scroller.visible = _data.length > num;
			}
			updateData(0);
		}
		
		/**
		 * Set the content of the list, as an array of Objects instances containing 'label' and 'data' keys. The 'label' key is used as displayed text into the list.
		 * Ex : [{label:"val 1", data:1}, {label:"val 2", data:2}]
		 */
		public function set data(data:Array):void
		{
			_data = data;
			if (data.length < _container.numChildren) numVisibleItems = data.length;
			else updateData(0);
			_scroller.steps = _data.length;
			_scroller.visible = _data.length > _container.numChildren;
		}
		
		/**
		 * Select the specified index in the list
		 * @param	index	The int index to select, pass -1 to deselect all.
		 */
		public function set selectedIndex(index:int):void
		{
			if (index < _data.length)
			{
				_selectedIndexes = index >= 0 ? [index] : [];
				updateData(_startIndex);
			}
		}
		
		/**
		 * Returns the index of the selected data.
		 */
		public function get selectedIndex():int
		{
			return _selectedIndexes.length > 0 ? _selectedIndexes[0] : -1;
		}
		
		/**
		 * Returns the indexes of the selected data.
		 */
		public function get selectedIndexes():Array
		{
			return _selectedIndexes;
		}
		
		/**
		 * Select the specified indexes in the list
		 * @param	indexes	an Array of ints indexes to select, pass empty array to deselect all.
		 */
		public function set selectedIndexes(indexes:/*int*/Array):void
		{
			_selectedIndexes = indexes;
			updateData(_startIndex);
		}
		
		/**
		 * Returns the 'data' key of the selected item, or null if no item is selected, or an array of the 'data' key of the selected items if allowMultiple is true.
		 * @return the 'data' key of the selected item
		 */
		public function getValue():*
		{
			if (allowMultiple)
			{
				var a:Array = [];
				for (var i:int = 0; i < _selectedIndexes.length; i++) a.push(_data[_selectedIndexes[i]]);
				return a;
			}
			return _data[selectedIndex];
		}
		
		/**
		 * Selects the specified index in the list
		 * @param	oValue	The int index to select, pass -1 to deselect all.
		 */
		public function setValue(oValue:*) : void
		{
			selectedIndex = uint(oValue);
		}
		
		/**
		 * Access to the scroller instance used by the current list
		 * @return	A scroller instance
		 */
		public function getScroller():Scroller
		{
			return _scroller;
		}
		
		/**
		 * updates the width of the list
		 */
		override public function set width(value:Number):void
		{
			_width = value;
			updateData(0);
		}
		
		/**
		 * Indicates if th cursor tracker must bes resized regarding the number of elements
		 */
		public function set resizeTracker(value:Boolean):void
		{
			_resizeTracker = value;
			if (_scroller) _scroller.setTrackerPercentHeight(_container.numChildren / _data.length);
		}
		
		/**
		 * Displays the current component as having an error or not. This method is automatically called by the Form class if form.useHighlight is set to true.
		 * @param	hasError	Indicates if there is an error or not.
		 */
		public function displayError(hasError:Boolean):void
		{
			_container.filters = hasError ? [new GlowFilter(0xFF0000, 1, 2, 2, 2, 3, true, false)] : null;
		}
		
		/**
		 * Activates the navigation with the keyboard UP/DOWN/ENTER keys.
		 * @param	bEnabled	true to enable keyboard navigation, false otherwise;
		 */
		public function enableKeyboard(bEnabled:Boolean):void
		{
			if (bEnabled)
			{
				Plugin.plugin.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
				addEventListener(Event.REMOVED, onRemoved);
			}
			else
			{
				Plugin.plugin.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
				removeEventListener(Event.REMOVED, onRemoved);
			}
		}
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		internal function getRendererClass():Class
		{
			return _rendererClass;
		}
		
		/**
		 * @private
		 */
		protected function createItem():ListItemRenderer
		{
			return new _rendererClass();
		}
		
		private function updateScrollerPos():void
		{
			_scroller.setValue(_startIndex / (_data.length - _container.numChildren));
		}
		
		private function updateData(startIndex:uint = 0):void
		{
			_startIndex = startIndex;
			var w:uint = _width;
			if (_scroller && _scroller.visible)
			{
				w -= _scroller.width;
				_scroller.x = w;
				if (_resizeTracker) _scroller.setTrackerPercentHeight(_container.numChildren / _data.length);
			}
			
			for (var i:int = 0; i < _container.numChildren; i++)
			{
				var item:ListItemRenderer = _container.getChildAt(i) as ListItemRenderer;
				var data:Object = _data[i + startIndex];
				if (data)
				{
					item.index = i + startIndex;
					item.setLabel(data.label);
					item.setData(data.data);
					if (_selectedIndexes.indexOf(i + startIndex) > -1) item.setSelected(true, !allowMultiple);
					else item.setSelected(false);
				}
				else
				{
					item.setLabel("");
					item.setSelected(false);
					item.lock(true);
				}
				
				item.width = w;
			}
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Events
		//
		//--------------------------------------------------------------------------
		private function onItemClicked(e:MouseEvent):void
		{
			if (allowMultiple)
			{
				var ind:int = _selectedIndexes.indexOf(e.target.index);
				if (ind == -1) _selectedIndexes.push(e.target.index);
				else _selectedIndexes.splice(ind, 1);
				updateData(_startIndex);
			}
			else selectedIndex = e.target.index;
			dispatchEvent(new Event(Event.CHANGE));
		}
		
		private function onScroll(e:Event):void
		{
			var startIndex:uint = Math.ceil((_data.length - _container.numChildren) * e.target.getValue());
			updateData(startIndex);
		}
		
		private function onRemoved(event:Event):void
		{
			enableKeyboard(false);
		}
		
		private function onKeyDown(event:KeyboardEvent):void
		{
			if (!allowMultiple)
			{
				if (event.keyCode == Keyboard.UP && selectedIndex > 0)
				{
					selectedIndex -= 1;
					if (selectedIndex < _startIndex)
					{
						updateData(selectedIndex);
						updateScrollerPos();
					}
				}
				else if (event.keyCode == Keyboard.DOWN)
				{
					selectedIndex += 1;
					if (selectedIndex == _startIndex + _container.numChildren)
					{
						updateData(_startIndex + 1);
						updateScrollerPos();
					}
				}
				else if (event.keyCode == Keyboard.ENTER) dispatchEvent(new Event(Event.CHANGE));
			}
		}
	}
}